<?php

/**
 * Main file defining the new module API.
 * Contains {@link module_base} class for modules and a utility function required for the
 * lib.php stubs.
 * @package resourcepage
 */

/** */
require_once(dirname(__FILE__).'/../../config.php');
require_once(dirname(__FILE__).'/../../course/lib.php');
require_once(dirname(__FILE__).'/../../lib/adodb/adodb-xmlschema.inc.php');

// Optional
@include_once(dirname(__FILE__).'/../../local/utils.php');
@include_once(dirname(__FILE__).'/../../local/module_access.php');

// OU shared APIs which (for OU system) are present in local, elsewhere
// are incorporated in module
@include_once(dirname(__FILE__).'/../../local/transaction_wrapper.php');
if (!class_exists('transaction_wrapper')) {
    require_once(dirname(__FILE__).'/null_transaction_wrapper.php');
}
@include_once(dirname(__FILE__).'/../../local/xml_backup.php');
if (!class_exists('xml_backup')) {
    require_once(dirname(__FILE__).'/local/xml_backup.php');
}
@include_once(dirname(__FILE__).'/../../local/utils_shared.php');
if (!function_exists('mkdir_recursive')) {
    require_once(dirname(__FILE__).'/local/utils_shared.php');
}

//libraries used by reference items, these are accessed in update_instance when the refstyle option is changed
if (file_exists(dirname(__FILE__).'/../../local/references/apibib/apibib_lib.php')) {
    @include_once(dirname(__FILE__).'/../../local/references/apibib/apibib_lib.php');
}
if (file_exists(dirname(__FILE__).'/../../local/references/convert/refxml.php')) {
    @include_once(dirname(__FILE__).'/../../local/references/convert/refxml.php');
}

/**
 * Base class for module interface.
 * When implementing this interface in a module, you are required to implement
 * the get_database_version abstract method.
 * Many of the other methods are also overrideable. Method names generally have
 * the same name as the Moodle module functions (and work the same way).
 * @package moduleapi
 */
abstract class module_base {

    private $name;

    /**
     * Constructs module object.
     */
    function __construct() {
        // Check classname and extract module name from it
        $classname=get_class($this);
        $matches=array();
        if(!preg_match('/^module_([a-z]+)$/',$classname,$matches)) {
            throw new Exception(
                'Module class name not valid; must be module_lowercaseletters: '.$classname); 
        }
        $this->name=$matches[1];
    }

    /**
     * @return string Name of module (same as folder name)
     */
    public function get_name() {
        return $this->name;
    }

    /**
     * Called to create an instance of this module.
     *
     * By default, just adds a record to the module's main table.
     * @param object $content Object containing form fields from the setup page.
     *   Can modify this to control what gets added to mdl_course_modules.
     * @return int ID (instance) of added item
     */
    public function add_instance(&$content) {

        $this->before_addupdate_instance($content,true);

        $return=insert_record($this->name, $content);
        if(!$return) {
            throw new Exception('Error creating database record');
        }

        $this->after_add_instance($return,$content);

        return $return;
    }

    /**
     * Called to update settings for an instance of this module.
     *
     * By default, just updates the database record.
     * @param object $content Object containing form fields from the setup page
     */
    public function update_instance($content) {
        $content->id=$content->instance;

        $this->before_addupdate_instance($content,false);

        //reference style update check
        $refupdate=false;
        if($content->refstyle!=get_field('resourcepage', 'refstyle', 'id', $content->id)){
            $refupdate=true;
        }

        if(!update_record($this->name, $content)) {
            throw new Exception('Error updating database record');
        }

        //if update to ref style (& classes loaded) update db with new titles based on new chosen style
        if($refupdate && class_exists('apibib') && class_exists('refxml')){
            //need to get all reference item on this rp (where not T)
            //resourcepage_containers where resourcepageid=id and containertype!=_section
            $containers=get_fieldset_select('resourcepage_containers','id','resourcepageid='.$content->id.' and containertype!=\'_section\'');


            //for each container all itemid (not empty)  in resourcepage_containeritems where containerid
            //returns array of all items in this resource page (itemid is in field of each element)
            $allitems=get_recordset_list('resourcepage_containeritems','containerid',implode(',',$containers),'','sequence,itemid');

            $refids=array();
            //go thru all items and add to list of all ids (need flatlist for get_recordset_list)
            foreach($allitems as $item){
                //ignore empty ids
                if($item['itemid']!=''){
                    $refids[]=$item['itemid'];
                }
            }
            //each resourcepage_items where itemid - get record for each item
            $allitems = array();
            if(isset($refids[0])){
                //each resourcepage_items where itemid - get record for each item
                $allitems=get_recordset_list('resourcepage_items','id',implode(',',$refids),'','id,itemtype,options,data');
            }
            
            $refstoupdate=array();
            $data='<?xml version="1.0" encoding="utf-8" standalone="no"?><references>';

            foreach($allitems as $item){
                if($item['itemtype']=='reference' && strpos($item['options'],'T')===false && strpos($item['data'],'<reference')!==false){
                    $refstoupdate[]=$item;
                    //get data for each, add to refxml(string)
                    $data.=$item['data'];
                }
            }
            $data.='</references>';

            if(count($refstoupdate)>0){
                global $CFG;
                //refxml prepare for quikbib
                $convertor=new refxml();
                $quikbibstr=$convertor->return_references($data,true);
                //setup quikbib and call data

                //put result from quikbib into db (if T not set in options)
                
                $teststyle=$content->refstyle;
                $teststyle=apibib::$referencestyles[$teststyle]['string'];
                
                //get reference titles from quikbib
                $titles=apibib::getbib($quikbibstr,$teststyle,'RefWorks XML');
                if(!is_array($titles)){
                    $titles=array();
                }
                
                //now save new titles (order should match refstoupdate)
                if(count($titles)==count($refstoupdate)){
                    for($a=0,$max=count($refstoupdate);$a<$max;$a++){
                        $title=addslashes($titles[$a]);
                        set_field('resourcepage_items','title',$title,'id',$refstoupdate[$a]['id']);
                    }
                }
            }
        }
    }

    /**
     * Overridable method that gets called before either adding or updating an
     * instance record. You can use this if the form fields from the setup page
     * might not match the fields that need to be added to the database.
     *
     * The base version fixes up live and remove dates if present, along with
     * other data that lives in the course_modules table.
     * @param &$content Object containing form fields from the setup page.
     * @param $isadd True for add, false for update
     */
    protected function before_addupdate_instance(&$content,$isadd) {
    }

    /**
     * Overridable method that gets called after adding an
     * instance record. You can use this if some setup needs to be
     * done after the instance ID is known.
     *
     * The base version does nothing.
     * @param $id ID of module
     * @param $content Object containing form fields from the setup page.
     */
    protected function after_add_instance($id,$content) {
    }

    /**
     * Called to return list of users with stuff in this module.
     *
     * By default, just returns false.
     * @param string $id Instance ID
     * @return mixed False if there is no such data.
     * @todo Find out what this method should actually return if it doesn't
     *   return false, and document that
     */
    public function get_instance_participants($id) {
        return false;
    }


    /**
     * Called to delete an instance of this module.
     * @param string $id Unique ID of module instance
     * @return bool True if instance was successfully deleted, false otherwise
     */
    public function delete_instance($id) {

        if (! $record = get_record($this->name, 'id', $id)) {
            throw new Exception('Requested module instance does not exist');
        }

        // Give subclass a chance to do something else
        $this->before_deleting_instance($record);

        if (! delete_records($this->name, 'id', $id)) {
            throw new Exception('Module instance could not be deleted');
        }

        // Give subclass a chance to do something else
        $this->after_deleting_instance($record);
    }

    /**
     * Called by delete_instance just before an instance is deleted.
     *
     * Default does nothing. Override to achieve specific behaviour
     * while retaining the standard delete that's done for you.
     * @param object $record Object of database fields
     */
    protected function before_deleting_instance($record) {
    }
    /**
     * Called by delete_instance just after an instance is deleted.
     *
     * Default does nothing. Override to achieve specific behaviour
     * while retaining the standard delete that's done for you.
     * @param object $record Object of database fields
     */
    protected function after_deleting_instance($record) {
    }

    /**
     * Called when the module gets cronned. Override to actually do
     * something. Default does nothing.
     */
    public function cron() {
    }

    /**
     * Called to get the database version of the module. Must be
     * overridden to return a string in the format YYYYMMDDnn e.g.
     * 2005112901. This version should change only when the database
     * content format changes i.e. it needs upgrading.
     * @return string Module database version string in standard format
     *   YYYYMMDDnn e.g. 2005112901
     */
    public abstract function get_database_version();

    /**
     * Called to check which Moodle version the module requires. Override
     * to change.
     * @return string Moodle version string in standard format (default
     *   2004052505)
     */
    public function get_required_version() {
        return '2004052505';
    }

    /**
     * @return string Filename of the 'edit' file for this module.
     */
    protected function get_edit_form_filename() {
        // This is a function rather than a constant or whatever, only so
        // it can easily be overridden.
        global $CFG;
        return $CFG->dirroot.'/mod/'.$this->name.'/mod_form.php';
    }

    /**
     * Displays the HTML content of the form used to edit instances
     * of this module.
     * @param object $form Object of current values for data. May be empty, or
     *   contain previous settings.
     * @param object $course Course object
     */
    public function show_edit_form($form,$course) {

        // Get coursemodule if present
        if($form->coursemodule) {
            if (! $cm = get_record('course_modules', 'id', $form->coursemodule)) {
                error('Failed to get course_modules data');
            }
        } else {
            $cm->notset=true; // So now it exists but is basically blank
        }

        $modmoodleform = $this->get_edit_form_filename();
        if (file_exists($modmoodleform)) {
            require_once($modmoodleform);

        } else {
            error('No formslib form description file found for this activity.');
        }

        $mformclassname = 'mod_'.$this->name.'_mod_form';
        $mform = new $mformclassname();
        $mform->set_data($form);
        $mform->display();

    }

    /**
     * Returns how often the module wants to be hit by cron job.
     * Default doesn't require cron access. Override to change. Note that
     * although it's specified in seconds, if the overall cron job runs
     * less frequently, you won't get it. Realistically, cron jobs may
     * occur e.g. every 5 minutes.
     * @return int Desired period between cron accesses, in seconds; 0
     *   if cron calls are not required.
     */
    public function get_cron_frequency() {
        return 0;
    }

    /**
     * Obtains basic information (database rows) to view an instance of
     * a module.
     * @param int $id Course-module ID passed as parameter (0 if none)
     * @param int $a Module instance ID passed as parameter (0 if none)
     * @param object &$cm Receives course-module row
     * @param object &$course Receives course row
     * @param object &$item Receives row from module's own main table
     */
    public function obtain_view_info($id,$a,&$cm,&$course,&$item) {
        if ($id) {
            if (! $cm = get_coursemodule_from_id($this->name, $id)) {
                error("Course Module ID was incorrect");
            }

            if (! $course = get_record("course", "id", $cm->course)) {
                error("Course is misconfigured");
            }

            if (! $item = get_record($this->name, "id", $cm->instance)) {
                error("Course module is incorrect");
            }

        } else {
            if (! $item = get_record($this->name, "id", $a)) {
                error("Course module is incorrect");
            }
            if (! $course = get_record("course", "id", $item->course)) {
                error("Course is misconfigured");
            }
            if (! $cm = get_coursemodule_from_instance($this->name, $item->id, $course->id)) {
                error("Course Module ID was incorrect");
            }
        }
    }

    /**
     * Shortcut to save standard view.php stuff when printing the header.
     * @param object $cm Row data from coursemodules table
     * @param object $course Row data from course table
     * @param object $item Row data from module's own table
     * @param string $extrabreadcrumb If supplied, puts an extra breadcrumb at end of header nav and
     *   makes the item one into a link.
     * @param string $updatebutton If true, includes 'update module' button
     * @param string $extrabuttons Additional buttons as well as the usual 'update module' one
     * @param string $focus If specified, gives form name e.g. inputform.password of an item that should
     *   be focused onload
     * @param string $meta Extra stuff to go into meta tags area
     * @param bool $cache Permits or denies caching of page (default true)
     * @param string $bodytags Extra stuff to go inside body tag (like the onload attribute for instance)
     */
    public function print_header($cm,$course,$item,$extrabreadcrumb='',$updatebutton=true,
    $extrabuttons='',$focus='', $meta='',$cache=true,$bodytags='',$extrabreadcrumbbefore='') {
        $stritems = get_string("modulenameplural", $this->name);
        $stritem = get_string("modulename", $this->name);

        $extranav = array();
        if ($extrabreadcrumb) {
            $extranav[] = array('name' => $extrabreadcrumb, 'link' => '', 'type' => 'title');
        }
        $navigation = build_navigation($extranav, $cm);
        print_header(
            "$course->shortname: $item->name", "$course->fullname",
        $navigation,
        $focus, $meta, $cache,
        $extrabuttons.($updatebutton ? update_module_button($cm->id, $course->id, $stritem) : ''),
        navmenu($course, $cm),false,$bodytags);
    }

    /**
     * Checks that user has access to view this module. If they don't,
     * calls the Moodle 'error' function which does not return.
     * @param object $cm Course modules table row
     * @param object $course Course table row
     */
    public function verify_view($cm,$course) {
        require_login($course->id,true,$cm);
        // Check if function exists for checking optional stealth mode, live dates and authids
        if (function_exists('is_module_student_accessible')) {
            list($accessible,$visible,$message)=is_module_student_accessible($cm,$course);
        } else {
            $accessible = $cm->visible;
        }
        if(!$accessible && !isteacher($course->id)) {
            error(get_string('denyaccess', 'resourcepage'));
        }
    }

    /**
     * Checks that user has access to perform an edit action on this module. If they don't,
     * calls the Moodle 'error' function which does not return.
     * @param object $cm Course modules table row
     */
    public function verify_edit_action($cm) {
        require_login($cm->course,true,$cm);
        if (!confirm_sesskey()) {
            error(get_string('confirmsesskeybad', 'error'));
        } else if (!isteacheredit($cm->course)) {
            error(get_string('onlyeditingteachers', 'error'));
        }
    }

    /**
     * Process a request for a file via requests to
     * /moodle/file.php/[course]/!via/[module]/...
     *
     * Default version just returns false.
     * @param int $courseid Course ID
     * @param array $args Arguments that come after the module name,
     *   one array element per path segment.
     * @return mixed New full path to file if supported, or false if none
     */
    public function process_file_request($courseid,$args) {
        return false;
    }

    /**
     * Handles backup for the module. Default doesn't implement it - must
     * be overridden to do anything. Do not call base class.
     * @param xml_backup $backup Backup object
     * @param object $preferences Backup preferences object
     * @param object $mainrow Row from module's main table
     * @param bool $userdata If true, include user data
     * @throws Exception (This implementation always throws an exception)
     */
    public function backup($backup,$preferences,$mainrow,$userdata) {
        throw new Exception('Backup is not supported!');
    }

    /**
     * Adds information to the 'info' array about the amount of user data
     * within a particular course or instance that would need to get backed up.
     * @param int $courseid Course ID
     * @param int $instance Instance ID
     * @param array &$info Info 2d array.
     * @param int $setkey Value of key to write. Set
     *   $info[$setkey][0] = name of data, $info[$setkey][1] = count of data
     */
    public function backup_get_userdata_info($courseid,$instance,&$info,$setkey) {
        return;
    }

    /**
     * Restores a module from backup. Not implemented in base class.
     * @param object $restoresettings Includes settings about the restore, such
     *   as ->courseid
     * @param array $xml XML array in slightly weird, but mostly usable, format
     * @param bool $userdata If true, restore user data too
     * @return int ID of newly-created entry in module table
     * @throws Exception (This implementation always throws an exception)
     */
    public function restore($restoresettings,$xml,$userdata) {
        throw new Exception('Restore is not supported!');
    }
    /**
     * Decodes links after restoring a module from backup. Default does nothing.
     * @param object $restoresettings Includes settings about the restore, such
     *   as ->courseid
     * @throws Exception If any error occurs
     */
    public function restore_decode_links($restoresettings) {
    }

    /**
     * Converts IDs from log entries. Default returns false.
     * @param object $restoresettings Includes settings about the restore, such
     *   as ->courseid
     * @param $log Log entry details
     * @return mixed False for failure, or a revised log entry
     */
    public function restore_log_entry($restoresettings,$log) {
        return false;
    }

    /** Cache of course-module ID to module ID or 'n' if none */
    private $moduleidcache=array();

    /**
     * Obtains the module ID based on a course-module ID. Uses
     * a cache to improve performance.
     * @param int $cmid ID in course-modules table
     * @return int ID on module table or false if it isn't present
     */
    public function get_moduleid_from_cmid($cmid) {
        if(isset($moduleidcache[$cmid])) {
            $value=$moduleidcache[$cmid];
            return $value==='n' ? false : $value;
        }

        $rs=$this->do_sql("
SELECT 
    cm.instance 
FROM 
    prefix_course_modules AS cm
    INNER JOIN prefix_modules AS m ON cm.module=m.id
WHERE
    cm.id=1000 
    AND m.name=".$this->c($this->get_name()));
        if($rs->EOF) {
            $this->moduleidcache[$cmid]='n';
            return false;
        }
        $this->moduleidcache[$cmid]=$rs->fields['instance'];
        return $rs->fields['instance'];
    }
}
?>